/**
 * Hook-Fetch Request Wrapper
 * 
 * Core wrapper class that provides request handling, deduplication, and tracking
 * functionality using hook-fetch library. This wrapper maintains compatibility
 * with existing axios-based patterns while leveraging modern fetch architecture.
 */

import type { 
  IHttpResponse, 
  IObject, 
  RequestConfig, 
  HookFetchWrapperConfig,
  PendingRequest,
  RequestIdentifierGenerator,
  HookFetchInstance
} from '../types/hookFetchTypes';
import { DUPLICATE_REQUEST_ERROR_CODE, DEFAULT_REQUEST_TIMEOUT } from '../types/hookFetchTypes';

/**
 * Core hook-fetch wrapper class that handles HTTP requests with deduplication
 * and tracking functionality
 */
export class HookFetchWrapper {
  private instance: HookFetchInstance;
  private requestPending: Map<string, PendingRequest> = new Map();
  private config: HookFetchWrapperConfig;
  private requestIdentifierGenerator: RequestIdentifierGenerator;

  /**
   * Initialize the hook-fetch wrapper with configuration
   * @param config - Wrapper configuration including baseURL, timeout, and plugins
   */
  constructor(config: HookFetchWrapperConfig) {
    this.config = {
      timeout: DEFAULT_REQUEST_TIMEOUT,
      ...config
    };
    
    // Initialize request identifier generator
    this.requestIdentifierGenerator = this.getDefaultRequestIdentifier;
    
    // Initialize hook-fetch instance (will be properly configured with plugins later)
    this.instance = this.createHookFetchInstance();
  }

  /**
   * Make an HTTP request with deduplication and tracking
   * @param config - Request configuration
   * @returns Promise that resolves to IHttpResponse
   */
  async request<T = any>(config: RequestConfig): Promise<IHttpResponse<T>> {
    // Generate unique identifier for this request
    const requestKey = this.getRequestIdentify(config);
    
    // Check for duplicate requests and handle deduplication
    if (this.handleRequestDeduplication(requestKey)) {
      return Promise.reject(new Error(`Duplicate request rejected with code: ${DUPLICATE_REQUEST_ERROR_CODE}`));
    }

    try {
      // Create the request promise
      const requestPromise = this.executeRequest<T>(config);
      
      // Track the pending request
      this.trackPendingRequest(requestKey, requestPromise);
      
      // Execute the request
      const response = await requestPromise;
      
      return response;
    } catch (error) {
      // Ensure cleanup happens even on error
      throw error;
    } finally {
      // Clean up the pending request tracking
      this.cleanupPendingRequest(requestKey);
    }
  }

  /**
   * Generate a unique identifier for the request based on method, URL, and parameters
   * @param config - Request configuration
   * @returns Unique request identifier string
   */
  private getRequestIdentify(config: RequestConfig): string {
    return this.requestIdentifierGenerator(config);
  }

  /**
   * Default request identifier generation logic
   * Creates identifier based on method, URL, params, and data
   * @param config - Request configuration
   * @returns Request identifier string
   */
  private getDefaultRequestIdentifier(config: RequestConfig): string {
    const method = config.method || 'GET';
    const url = config.url;
    
    // Create a stable string representation of params and data
    const paramsStr = config.params ? JSON.stringify(this.sortObject(config.params)) : '';
    const dataStr = config.data ? JSON.stringify(this.sortObject(config.data)) : '';
    
    // Combine all components to create unique identifier
    return `${method}:${url}:${paramsStr}:${dataStr}`;
  }

  /**
   * Sort object keys to ensure consistent string representation
   * @param obj - Object to sort
   * @returns Sorted object
   */
  private sortObject(obj: IObject): IObject {
    if (!obj || typeof obj !== 'object') return obj;
    
    const sorted: IObject = {};
    Object.keys(obj).sort().forEach(key => {
      sorted[key] = obj[key];
    });
    return sorted;
  }

  /**
   * Handle request deduplication logic
   * @param requestKey - Unique request identifier
   * @returns true if request should be rejected as duplicate, false otherwise
   */
  private handleRequestDeduplication(requestKey: string): boolean {
    // Check if there's already a pending request with the same identifier
    if (this.requestPending.has(requestKey)) {
      const pendingRequest = this.requestPending.get(requestKey);
      
      // Verify the pending request is still active
      if (pendingRequest && this.isPendingRequestActive(pendingRequest)) {
        return true; // Reject as duplicate
      } else {
        // Clean up stale pending request
        this.requestPending.delete(requestKey);
      }
    }
    
    return false; // Allow the request
  }

  /**
   * Check if a pending request is still active
   * @param pendingRequest - Pending request to check
   * @returns true if request is still active
   */
  private isPendingRequestActive(pendingRequest: PendingRequest): boolean {
    // For now, we assume all tracked requests are active
    // In a more sophisticated implementation, we could check promise state
    return true;
  }

  /**
   * Track a pending request
   * @param requestKey - Request identifier
   * @param requestPromise - Request promise to track
   */
  private trackPendingRequest(requestKey: string, requestPromise: Promise<any>): void {
    const pendingRequest: PendingRequest = {
      key: requestKey,
      promise: requestPromise,
      timestamp: Date.now()
    };
    
    this.requestPending.set(requestKey, pendingRequest);
  }

  /**
   * Clean up a pending request from tracking
   * @param requestKey - Request identifier to clean up
   */
  private cleanupPendingRequest(requestKey: string): void {
    this.requestPending.delete(requestKey);
  }

  /**
   * Execute the actual HTTP request using hook-fetch
   * @param config - Request configuration
   * @returns Promise that resolves to IHttpResponse
   */
  private async executeRequest<T = any>(config: RequestConfig): Promise<IHttpResponse<T>> {
    try {
      // Merge with default configuration
      const requestConfig: RequestConfig = {
        ...config,
        timeout: config.timeout || this.config.timeout
      };

      // Execute request through hook-fetch instance
      const { result } = await this.instance.request<T>(requestConfig);
      
      return result;
    } catch (error) {
      // Re-throw error to be handled by calling code
      throw error;
    }
  }

  /**
   * Create hook-fetch instance (placeholder for now)
   * This will be properly implemented when plugins are available
   * @returns Mock hook-fetch instance
   */
  private createHookFetchInstance(): HookFetchInstance {
    // This is a placeholder implementation
    // The actual hook-fetch instance will be created when plugins are implemented
    return {
      request: async <T = any>(config: RequestConfig) => {
        // Placeholder implementation - will be replaced with actual hook-fetch
        throw new Error('Hook-fetch instance not yet configured with plugins');
      },
      use: (plugin: any) => {
        // Placeholder for plugin registration
      }
    };
  }

  /**
   * Get current pending requests count (for debugging/monitoring)
   * @returns Number of currently pending requests
   */
  public getPendingRequestsCount(): number {
    return this.requestPending.size;
  }

  /**
   * Get all pending request keys (for debugging/monitoring)
   * @returns Array of pending request identifiers
   */
  public getPendingRequestKeys(): string[] {
    return Array.from(this.requestPending.keys());
  }

  /**
   * Clear all pending requests (useful for cleanup or testing)
   */
  public clearPendingRequests(): void {
    this.requestPending.clear();
  }

  /**
   * Set custom request identifier generator
   * @param generator - Custom identifier generation function
   */
  public setRequestIdentifierGenerator(generator: RequestIdentifierGenerator): void {
    this.requestIdentifierGenerator = generator;
  }

  /**
   * Get wrapper configuration
   * @returns Current wrapper configuration
   */
  public getConfig(): HookFetchWrapperConfig {
    return { ...this.config };
  }
}